/* 
 * Copyright 2009 Shared Object (www.sharedobject.ch)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package edos.prm.engine;

import java.util.Set;

import edos.prm.activity.Activity;
import edos.prm.activity.Operation;
import edos.prm.activity.Operation.PrmOperation;
import edos.prm.actor.Actor;
import edos.prm.role.Right;
import edos.prm.role.Role;
import edos.prm.role.RoleException;
import edos.prm.task.Task;
import edos.prm.task.TaskManager.PrmTaskManager;

/**
 * The {@code PrmSecurityManager} is a security manager implementation used by
 * the default Java implementation of the PRM to enforce security properties.
 * <p>
 * The list of security properties to respect are the following:
 * <ul>
 * <li><B>Role Assignment</B>. A task may execute an operation only if it
 * has been assigned a role that permits this operation.</li>
 * <li><B>Artifact Containment</B>. An artifact can only be manipulated by
 * its managing activity or by trusted code. For instance, instead of
 * manipulating activities directly, all calls must go through
 * {@code ActivityManager}.</li>
 * <li><B>Activity Mediation</B>. An invocation of an activity's operation
 * must be mediated by the PRM - so that role checking can be made and any
 * metrics bound to the operation get evaluated.</li>
 * </ul>
 *
 * @version 1.0
 */
public final class PrmSecurityManager extends SecurityManager {

	/**
	 * Verifies that the task of the current thread has been assigned the roles
	 * needed to execute this operation.
	 * 
	 * @param operation an Operation for which the task of the current thread
	 * needs to have a role to execute.
	 * @throws RoleException if the task does not have a role for the operation
	 */
	public void checkRoleAssigned(Operation operation) throws RoleException {
		// Ensure activity mediation. The caller is at position 2 in the call
		// stack
		Class<?>[] clazzes = this.getClassContext();
//		System.out.println("Stack for operation " + operation.getName());
//		for ( int i = 0; i < clazzes.length; i++ )
//			System.out.println("\tclass is " + clazzes[i]);
		// TODO - clean up this logic
		//		if  ( ! ( Activity.class.isAssignableFrom(clazzes[2]) ||
		//				( clazzes[2].getName().equals(PRM.getProgramName())) ||
		//				Operation.class.isAssignableFrom(clazzes[2]) ||
		//				clazzes[2].getName().startsWith("edos.prm") ) )
		//			throw new SecurityException("Violation of activity mediation");

		if ( !(Operation.class.isAssignableFrom(clazzes[2])) )
			return;

		Task task = PrmTaskManager.getTask(Thread.currentThread());
		Actor actor = task.getActor();

		Set<Role> roles = actor.getRoles();
		for ( Role role : roles )
			for ( Right r : role.getRights() ) {
				if ( r.equals(Right.AllRights ))
					return;
				for ( Operation op : r.getOperations() )
					if ( op.equals(operation) )
						return;
			}

		throw new RoleException("Role not assigned - " 
				+ actor.getContactInformation().getName());
	}

	/**
	 * Ensures that the target object is being manipulated by an activity of
	 * the specified caller class.
	 * 
	 * @param caller a class of a program that manipulates the target
	 * @param target the class of the object being manipulated
	 * @throws SecurityException if the target is not being manipulated by the
	 * caller class.
	 */
	public void verifyArtifactContainment(Class<?> caller, Class<?> target)
	throws SecurityException {
		// Inspect the call stack - the array goes from the top of the call
		// stack tot he bottom
		Class<?>[] clazzes = this.getClassContext();
		int i = 0;
		boolean found = false;
		while ( (!found) && ( i < clazzes.length ) ) {
			if ( clazzes[i].equals(target) )
				found = true;
			else
				i++;
		}
		if ( !found || ( i >= clazzes.length ) )
			throw new SecurityException("Improper call on target");

		if ( caller.isAssignableFrom(clazzes[i+1])
				&& target.isAssignableFrom(clazzes[i+1]) )
			return;
		// Exception to rule is the core PRM classes
		if ( caller.getName().startsWith("edos.prm") )
			return;

		throw new SecurityException("Containment violation");
	}

	/**
	 * Called by activities to ensure that the calling task has been attributed
	 * a role that permits an operation to be called.
	 * 
	 * @param operation the operation being called
	 * @param activity the calling activity
	 * @param types the types of the arguments to the operation
	 * @throws RoleException if the task does not have a role that permits the
	 * operation to be called
	 */
	public static void roleCheck(String operation,
			Class<? extends Activity> activity, Class<?>...types)
	throws RoleException {
		try {
			PrmSecurityManager sm =
				(PrmSecurityManager) System.getSecurityManager();
			if ( sm != null ) {
				sm.checkRoleAssigned(PrmOperation.
						getOperation(activity.getDeclaredMethod(operation, types)));
			}
		} catch (NoSuchMethodException nsme) {
			nsme.printStackTrace();
		}
	}

}
